/** @file
  The assistant function implementation for IpSecConfig application.

  Copyright (c) 2009 - 2012, Intel Corporation. All rights reserved.<BR>

  SPDX-License-Identifier: BSD-2-Clause-Patent

**/

#include "IpSecConfig.h"
#include "Helper.h"

/**
  Helper function called to change an input parameter in the string format to a number.

  @param[in]      FlagStr         The pointer to the flag string.
  @param[in]      Maximum         Greatest value number.
  @param[in, out] ValuePtr        The pointer to the input parameter in string format.
  @param[in]      ByteCount       The valid byte count
  @param[in]      Map             The pointer to the STR2INT table.
  @param[in]      ParamPackage    The pointer to the ParamPackage list.
  @param[in]      FormatMask      The bit mask.
                                  BIT 0 set indicates the value of a flag might be a number.
                                  BIT 1 set indicates the value of a flag might be a string that needs to be looked up.

  @retval EFI_SUCCESS              The operation completed successfully.
  @retval EFI_NOT_FOUND            The input parameter can't be found.
  @retval EFI_INVALID_PARAMETER    The input parameter is an invalid input.
**/
EFI_STATUS
GetNumber (
  IN     CHAR16        *FlagStr,
  IN     UINT64        Maximum,
  IN OUT VOID          *ValuePtr,
  IN     UINTN         ByteCount,
  IN     STR2INT       *Map,
  IN     LIST_ENTRY    *ParamPackage,
  IN     UINT32        FormatMask
  )
{
  EFI_STATUS      Status;
  UINT64          Value64;
  BOOLEAN         Converted;
  UINTN           Index;
  CONST CHAR16    *ValueStr;

  ASSERT (FormatMask & (FORMAT_NUMBER | FORMAT_STRING));

  Converted = FALSE;
  Value64   = 0;
  ValueStr  = ShellCommandLineGetValue (ParamPackage, FlagStr);

  if (ValueStr == NULL) {
    return EFI_NOT_FOUND;
  } else {
    //
    // Try to convert to integer directly if MaybeNumber is TRUE.
    //
    if ((FormatMask & FORMAT_NUMBER) != 0) {
      Value64 = StrToUInteger (ValueStr, &Status);
      if (!EFI_ERROR (Status)) {
        //
        // Convert successfully.
        //
        if (Value64 > Maximum) {
          //
          // But the result is invalid
          //
          ShellPrintHiiEx (
            -1,
            -1,
            NULL,
            STRING_TOKEN (STR_IPSEC_CONFIG_INCORRECT_PARAMETER_VALUE),
            mHiiHandle,
            mAppName,
            FlagStr,
            ValueStr
            );
          return EFI_INVALID_PARAMETER;
        }

        Converted = TRUE;
      }
    }

    if (!Converted && ((FormatMask & FORMAT_STRING) != 0)) {
      //
      // Convert falied, so use String->Integer map.
      //
      ASSERT (Map != NULL);
      Value64 = MapStringToInteger (ValueStr, Map);
      if (Value64 == (UINT32) -1) {
        //
        // Cannot find the string in the map.
        //
        ShellPrintHiiEx (
          -1,
          -1,
          NULL,
          STRING_TOKEN (STR_IPSEC_CONFIG_INCORRECT_PARAMETER_VALUE),
          mHiiHandle,
          mAppName,
          FlagStr,
          ValueStr
          );
        ShellPrintHiiEx (-1, -1, NULL, STRING_TOKEN (STR_IPSEC_CONFIG_ACCEPT_PARAMETERS), mHiiHandle);
        for (Index = 0; Map[Index].String != NULL; Index++) {
          Print (L" %s", Map[Index].String);
        }

        Print (L"\n");
        return EFI_INVALID_PARAMETER;
      }
    }

    CopyMem (ValuePtr, &Value64, ByteCount);
    return EFI_SUCCESS;
  }
}

/**
  Helper function called to convert a string containing an Ipv4 or Ipv6 Internet Protocol address
  into a proper address for the EFI_IP_ADDRESS structure.

  @param[in]  Ptr    The pointer to the string containing an Ipv4 or Ipv6 Internet Protocol address.
  @param[out] Ip     The pointer to the EFI_IP_ADDRESS structure to contain the result.

  @retval EFI_SUCCESS              The operation completed successfully.
  @retval EFI_INVALID_PARAMETER    Invalid parameter.
**/
EFI_STATUS
EfiInetAddr2 (
  IN  CHAR16            *Ptr,
  OUT EFI_IP_ADDRESS    *Ip
  )
{
  EFI_STATUS    Status;

  if ((Ptr == NULL) || (Ip == NULL)) {
    return EFI_INVALID_PARAMETER;
  }

  //
  // Parse the input address as Ipv4 Address first.
  //
  Status = NetLibStrToIp4 (Ptr, &Ip->v4);
  if (!EFI_ERROR (Status)) {
    return Status;
  }

  Status = NetLibStrToIp6 (Ptr, &Ip->v6);
  return Status;
}

/**
  Helper function called to calculate the prefix length associated with the string
  containing an Ipv4 or Ipv6 Internet Protocol address.

  @param[in]  Ptr     The pointer to the string containing an Ipv4 or Ipv6 Internet Protocol address.
  @param[out] Addr    The pointer to the EFI_IP_ADDRESS_INFO structure to contain the result.

  @retval EFI_SUCCESS              The operation completed successfully.
  @retval EFI_INVALID_PARAMETER    Invalid parameter.
  @retval Others                   Other mistake case.
**/
EFI_STATUS
EfiInetAddrRange (
  IN  CHAR16                 *Ptr,
  OUT EFI_IP_ADDRESS_INFO    *Addr
  )
{
  EFI_STATUS    Status;

  if ((Ptr == NULL) || (Addr == NULL)) {
    return EFI_INVALID_PARAMETER;
  }

  Status = NetLibStrToIp4 (Ptr, &Addr->Address.v4);
  if (!EFI_ERROR (Status)) {
    if ((UINT32)(*Addr->Address.v4.Addr) == 0) {
      Addr->PrefixLength = 0;
    } else {
      Addr->PrefixLength = 32;
    }
    return Status;
  }

  Status = NetLibStrToIp6andPrefix (Ptr, &Addr->Address.v6, &Addr->PrefixLength);
  if (!EFI_ERROR (Status) && (Addr->PrefixLength == 0xFF)) {
    Addr->PrefixLength = 128;
  }

  return Status;
}

/**
  Helper function called to calculate the port range associated with the string.

  @param[in]  Ptr          The pointer to the string containing a port and range.
  @param[out] Port         The pointer to the Port to contain the result.
  @param[out] PortRange    The pointer to the PortRange to contain the result.

  @retval EFI_SUCCESS              The operation completed successfully.
  @retval EFI_INVALID_PARAMETER    Invalid parameter.
  @retval Others                   Other mistake case.
**/
EFI_STATUS
EfiInetPortRange (
  IN  CHAR16    *Ptr,
  OUT UINT16    *Port,
  OUT UINT16    *PortRange
  )
{
  CHAR16        *BreakPtr;
  CHAR16        Ch;
  EFI_STATUS    Status;

  for (BreakPtr = Ptr; (*BreakPtr != L'\0') && (*BreakPtr != L':'); BreakPtr++) {
    ;
  }

  Ch        = *BreakPtr;
  *BreakPtr = L'\0';
  *Port     = (UINT16) StrToUInteger (Ptr, &Status);
  *BreakPtr = Ch;
  if (EFI_ERROR (Status)) {
    return Status;
  }

  *PortRange = 0;
  if (*BreakPtr == L':') {
    BreakPtr++;
    *PortRange = (UINT16) StrToUInteger (BreakPtr, &Status);
    if (EFI_ERROR (Status)) {
      return Status;
    }

    if (*PortRange < *Port) {
      return EFI_INVALID_PARAMETER;
    }

    *PortRange = (UINT16) (*PortRange - *Port);
  }

  return EFI_SUCCESS;
}

/**
  Helper function called to transfer a string to an unsigned integer.

  @param[in]  Str       The pointer to the string.
  @param[out] Status    The operation status.

  @return The integer value of converted Str.
**/
UINT64
StrToUInteger (
  IN  CONST CHAR16    *Str,
  OUT EFI_STATUS      *Status
  )
{
  UINT64    Value;
  UINT64    NewValue;
  CHAR16    *StrTail;
  CHAR16    Char;
  UINTN     Base;
  UINTN     Len;

  Base    = 10;
  Value   = 0;
  *Status = EFI_ABORTED;

  //
  // Skip leading white space.
  //
  while ((*Str != 0) && (*Str == ' ')) {
    Str++;
  }
  //
  // For NULL Str, just return.
  //
  if (*Str == 0) {
    return 0;
  }
  //
  // Skip white space in tail.
  //
  Len     = StrLen (Str);
  StrTail = (CHAR16 *) (Str + Len - 1);
  while (*StrTail == ' ') {
    *StrTail = 0;
    StrTail--;
  }

  Len = StrTail - Str + 1;

  //
  // Check hex prefix '0x'.
  //
  if ((Len >= 2) && (*Str == '0') && ((*(Str + 1) == 'x') || (*(Str + 1) == 'X'))) {
    Str += 2;
    Len -= 2;
    Base = 16;
  }

  if (Len == 0) {
    return 0;
  }
  //
  // Convert the string to value.
  //
  for (; Str <= StrTail; Str++) {

    Char = *Str;

    if (Base == 16) {
      if (RShiftU64 (Value, 60) != 0) {
        //
        // Overflow here x16.
        //
        return 0;
      }

      NewValue = LShiftU64 (Value, 4);
    } else {
      if (RShiftU64 (Value, 61) != 0) {
        //
        // Overflow here x8.
        //
        return 0;
      }

      NewValue  = LShiftU64 (Value, 3);
      Value     = LShiftU64 (Value, 1);
      NewValue += Value;
      if (NewValue < Value) {
        //
        // Overflow here.
        //
        return 0;
      }
    }

    Value = NewValue;

    if ((Base == 16) && (Char >= 'a') && (Char <= 'f')) {
      Char = (CHAR16) (Char - 'a' + 'A');
    }

    if ((Base == 16) && (Char >= 'A') && (Char <= 'F')) {
      Value += (Char - 'A') + 10;
    } else if ((Char >= '0') && (Char <= '9')) {
      Value += (Char - '0');
    } else {
      //
      // Unexpected Char encountered.
      //
      return 0;
    }
  }

  *Status = EFI_SUCCESS;
  return Value;
}

/**
  Helper function called to transfer a string to an unsigned integer according to the map table.

  @param[in] Str    The pointer to the string.
  @param[in] Map    The pointer to the map table.

  @return The integer value of converted Str. If not found, then return -1.
**/
UINT32
MapStringToInteger (
  IN CONST CHAR16    *Str,
  IN STR2INT         *Map
  )
{
  STR2INT       *Item;

  for (Item = Map; Item->String != NULL; Item++) {
    if (StrCmp (Item->String, Str) == 0) {
      return Item->Integer;
    }
  }

  return (UINT32) -1;
}

/**
  Helper function called to transfer an unsigned integer to a string according to the map table.

  @param[in] Integer    The pointer to the string.
  @param[in] Map        The pointer to the map table.

  @return The converted Str. If not found, then return NULL.
**/
CHAR16 *
MapIntegerToString (
  IN UINT32     Integer,
  IN STR2INT    *Map
  )
{
  STR2INT    *Item;

  for (Item = Map; Item->String != NULL; Item++) {
    if (Integer == Item->Integer) {
      return Item->String;
    }
  }

  return NULL;
}
